/*	Copyright (c) 2018 Jean-Marc VIGLINO,
	released under the CeCILL-B license (French BSD license)
	(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/
import ol_control_Control from 'ol/control/Control.js'
import ol_format_WKT from 'ol/format/WKT.js'
import ol_format_GeoJSON from 'ol/format/GeoJSON.js'
import {toLonLat as ol_proj_toLonLat} from 'ol/proj.js'
import ol_ext_Ajax from '../util/Ajax.js';
import ol_ext_element from '../util/element.js';
import ol_control_SearchGeoportail from './SearchGeoportail.js'

/**
 * Geoportail isochrone Control.
 * @see https://geoservices.ign.fr/documentation/geoservices/isochrones.html
 * @constructor
 * @extends {ol_control_Control}
 * @fires isochrone
 * @fires error
 * @param {Object=} options
 *	@param {string} options.className control class name
 *	@param {string} [options.apiKey] Geoportail apo key
 *	@param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport.
 *	@param {string | undefined} options.label Text label to use for the search button, default "search"
 *	@param {string | undefined} options.placeholder placeholder, default "Search..."
 *	@param {string | undefined} options.inputLabel label for the input, default none
 *	@param {string | undefined} options.noCollapse prevent collapsing on input blur, default false
 *	@param {number | undefined} options.typing a delay on each typing to start searching (ms) use -1 to prevent autocompletion, default 300.
 *	@param {integer | undefined} options.minLength minimum length to start searching, default 1
 *	@param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10
 *	@param {integer | undefined} options.maxHistory maximum number of items to display in history. Set -1 if you don't want history, default maxItems
 *	@param {function} options.getTitle a function that takes a feature and return the name to display in the index.
 *	@param {function} options.autocomplete a function that take a search string and callback function to send an array
 *
 *  @param {string} options.exclusions Exclusion list separate with a comma 'Toll,Tunnel,Bridge'
 */
var ol_control_IsochroneGeoportail = class olcontrolIsochroneGeoportail extends ol_control_Control {
  constructor(options) {
    if (!options) options = {};
    if (options.typing == undefined) options.typing = 300;

    var classNames = (options.className ? options.className : '') + ' ol-isochrone ol-routing';
    if (!options.target) classNames += ' ol-unselectable ol-control';
    var element = ol_ext_element.create('DIV', { className: classNames });

    if (!options.target) {
      var bt = ol_ext_element.create('BUTTON', { parent: element });
      bt.addEventListener('click', function () {
        element.classList.toggle('ol-collapsed');
      });
    }

    // Inherits
    super({
      element: element,
      target: options.target
    });

    var self = this;
    this.set('iter', 1);

    var content = ol_ext_element.create('DIV', { className: 'content', parent: element });
    // Search control
    this._addSearchCtrl(content, options);

    // Method buttons
    ol_ext_element.create('BUTTON', { className: 'ol-button ol-method-time selected', title: 'isochrone', parent: content })
      .addEventListener('click', function () {
        this.setMethod('time');
      }.bind(this));
    ol_ext_element.create('I', { className: 'ol-button ol-method-distance', title: 'isodistance', parent: content })
      .addEventListener('click', function () {
        this.setMethod('distance');
      }.bind(this));

    // Mode buttons
    ol_ext_element.create('I', { className: 'ol-button ol-car selected', title: 'by car', parent: content })
      .addEventListener('click', function () {
        this.setMode('car');
      }.bind(this));
    ol_ext_element.create('I', { className: 'ol-button ol-pedestrian', title: 'by foot', parent: content })
      .addEventListener('click', function () {
        this.setMode('pedestrian');
      }.bind(this));

    // Direction buttons
    ol_ext_element.create('I', { className: 'ol-button ol-direction-direct selected', title: 'direct', parent: content })
      .addEventListener('click', function () {
        this.setDirection('direct');
      }.bind(this));
    ol_ext_element.create('I', { className: 'ol-button ol-direction-reverse', title: 'reverse', parent: content })
      .addEventListener('click', function () {
        this.setDirection('reverse');
      }.bind(this));

    // Input 
    var div = ol_ext_element.create('DIV', { className: 'ol-time', parent: content });
    ol_ext_element.create('DIV', { html: 'isochrone:', parent: div });
    ol_ext_element.create('INPUT', { type: 'number', parent: div, min: 0 })
      .addEventListener('change', function () {
        self.set('hour', Number(this.value));
      });
    ol_ext_element.create('TEXT', { parent: div, html: 'h' });
    ol_ext_element.create('INPUT', { type: 'number', parent: div, min: 0 })
      .addEventListener('change', function () {
        self.set('minute', Number(this.value));
      });
    ol_ext_element.create('TEXT', { parent: div, html: 'mn' });
    div = ol_ext_element.create('DIV', { className: 'ol-distance', parent: content });
    ol_ext_element.create('DIV', { html: 'isodistance:', parent: div });
    ol_ext_element.create('INPUT', { type: 'number', step: 'any', parent: div, min: 0 })
      .addEventListener('change', function () {
        self.set('distance', Number(this.value));
      });
    ol_ext_element.create('TEXT', { parent: div, html: 'km' });

    div = ol_ext_element.create('DIV', { className: 'ol-iter', parent: content });
    ol_ext_element.create('DIV', { html: 'Iteration:', parent: div });
    ol_ext_element.create('INPUT', { type: 'number', parent: div, value: 1, min: 1 })
      .addEventListener('change', function () {
        self.set('iter', Number(this.value));
      });

    // OK button
    ol_ext_element.create('I', { className: 'ol-ok', html: 'ok', parent: content })
      .addEventListener('click', function () {
        var val = 0;
        switch (this.get('method')) {
          case 'distance': {
            val = this.get('distance') * 1000;
            break;
          }
          default: {
            val = (this.get('hour') || 0) * 3600 + (this.get('minute') || 0) * 60;
            break;
          }
        }
        if (val && this.get('coordinate')) {
          this.search(this.get('coordinate'), val);
        }
      }.bind(this));

    this.set('url', 'https://data.geopf.fr/navigation/isochrone')
    this._ajax = new ol_ext_Ajax({
      dataType: 'JSON',
      auth: options.auth
    });

    this._ajax.on('success', this._success.bind(this));
    this._ajax.on('error', this._error.bind(this));
    // searching
    this._ajax.on('loadstart', function () {
      this.element.classList.add('ol-searching');
    }.bind(this));
    this._ajax.on('loadend', function () {
      this.element.classList.remove('ol-searching');
    }.bind(this));

    this.setMethod(options.method);
  }
  /**
   * Set the map instance the control is associated with
   * and add its controls associated to this map.
   * @param {_ol_Map_} map The map instance.
   */
  setMap(map) {
    super.setMap(map);
    this._search.setMap(map);
  }
  /** Add a new search input
   * @private
   */
  _addSearchCtrl(element, options) {
    var div = ol_ext_element.create("DIV", { parent: element });

    var search = this._search = new ol_control_SearchGeoportail({
      className: 'IGNF ol-collapsed',
      apiKey: options.apiKey,
      target: div
    });
    search.on('select', function (e) {
      search.setInput(e.search.fulltext);
      this.set('coordinate', e.coordinate);
    }.bind(this));
    search.on('change:input', function () {
      this.set('coordinate', false);
    }.bind(this));
  }
  /** Set the travel method
   * @param [string] method The method (time or distance)
   */
  setMethod(method) {
    method = (/distance/.test(method) ? 'distance' : 'time');
    this.element.querySelector(".ol-method-time").classList.remove("selected");
    this.element.querySelector(".ol-method-distance").classList.remove("selected");
    this.element.querySelector(".ol-method-" + method).classList.add("selected");
    this.element.querySelector("div.ol-time").classList.remove("selected");
    this.element.querySelector("div.ol-distance").classList.remove("selected");
    this.element.querySelector("div.ol-" + method).classList.add("selected");
    this.set('method', method);
  }
  /** Set mode
   * @param {string} mode The mode: 'car' or 'pedestrian', default 'car'
   */
  setMode(mode) {
    this.set('mode', mode);
    this.element.querySelector(".ol-car").classList.remove("selected");
    this.element.querySelector(".ol-pedestrian").classList.remove("selected");
    this.element.querySelector(".ol-" + mode).classList.add("selected");
  }
  /** Set direction
   * @param {string} direction The direction: 'direct' or 'reverse', default direct
   */
  setDirection(direction) {
    this.set('direction', direction);
    this.element.querySelector(".ol-direction-direct").classList.remove("selected");
    this.element.querySelector(".ol-direction-reverse").classList.remove("selected");
    this.element.querySelector(".ol-direction-" + direction).classList.add("selected");
  }
  /** Calculate an isochrone
   * @param {ol.coordinate} coord
   * @param {number|string} option A number as time (in second) or distance (in meter), depend on method propertie
   * or a string with a unit (s, mn, h for time or km, m)
   */
  search(coord, option, iter) {
    var proj = this.getMap() ? this.getMap().getView().getProjection() : 'EPSG:3857';
    var method = /distance/.test(this.get('method')) ? 'distance' : 'time';
    if (typeof (option) === 'string') {
      var unit = option.replace(/([0-9|.]*)([a-z]*)$/, '$2');
      method = 'time';
      option = parseFloat(option);
      // convert unit
      switch (unit) {
        case 'mn': {
          option = option * 60;
          break;
        }
        case 'h': {
          option = option * 3600;
          break;
        }
        case 'm': {
          method = 'distance';
          break;
        }
        case 'km': {
          method = 'distance';
          option = option * 1000;
          break;
        }
      }
    }
    var dt = Math.round(option * (this.get('iter') - (iter || 0)) / this.get('iter'));
    if (typeof option === 'number') {
      // Send data => Capabilities: https://data.geopf.fr/navigation/getcapabilities
      var data = {
        // resource: 'jmk_valhalla_cost_type_test', 
        resource: 'bdtopo-valhalla',
        point: ol_proj_toLonLat(coord, proj),
        profile: this.get('mode') === 'pedestrian' ? 'pedestrian' : 'car',
        costType: method,
        costValue: dt,
        geometryFormat: 'geojson',
        direction: (this.get('direction') === 'reverse') ? 'arrival' : 'departure',
        // crs: 'EPSG:4326'
      };
      this._ajax.send(this.get('url'), data, {
        coord: coord,
        option: option,
        data: data,
        iteration: (iter || 0) + 1
      });
    }
  }
  /** Trigger result
   * @private
   */
  _success(e) {
    console.log(e)
    var proj = this.getMap() ? this.getMap().getView().getProjection() : 'EPSG:3857';
    // Convert to features
    var evt = e.response;
    var format;
    if (evt.wktGeometry) {
      format = new ol_format_WKT();
      evt.feature = format.readFeature(evt.wktGeometry, {
        dataProjection: 'EPSG:4326',
        featureProjection: proj
      });
      delete evt.wktGeometry;
    } else {
      format = new ol_format_GeoJSON();
      evt.feature = format.readFeature(evt.geometry, {
        dataProjection: 'EPSG:4326',
        featureProjection: proj
      });
      delete evt.geometry;
    }
    evt.feature.set('iteration', e.options.iteration);
    evt.feature.set('method', e.options.data.method);
    evt.feature.set(e.options.data.method, e.options.data[e.options.data.method]);
    evt.type = 'isochrone';
    evt.iteration = e.options.iteration - 1;
    this.dispatchEvent(evt);
    if (e.options.iteration < this.get('iter')) {
      this.search(e.options.coord, e.options.option, e.options.iteration);
    }
  }
  /** Trigger error
   * @private
   */
  _error() {
    this.dispatchEvent({ type: 'error' });
  }
}

export default ol_control_IsochroneGeoportail;